package oneD.models;

import inf.jlinalg.ArrayVector;
import inf.jlinalg.BLAM;
import inf.jlinalg.IVector;
import inf.jlinalg.MatrixFormat;
import inf.jlinalg.SolveFailedException;
import inf.v3d.view.Viewer;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JButton;

import oneD.dynamics.DynamicStructure;
import oneD.dynamics.LinearTimeIntegrator;
import oneD.fem.method.ArcLengthControl;
import oneD.fem.method.ModalAnalyzer;
import oneD.fem.structure.Structure;
import oneD.fem.visualize.Visualizer;

public class ModelViewer {
	protected Viewer view_;
	protected Visualizer viz_;
	protected double d_max_; // the maximum distance between 2 points in the
								// mesh
	protected String[] color_ = new String[] { "green", "blue", "cyan",
			"yellow", "gray", "pearl", "pink", "wine", "violet", "ruby", "",
			"", "", "", "" };

	private enum Functionality {
		VIEW, LIN_STA, LIN_MODAL, LIN_DYNAMIC, NLIN_STA, NLIN_INS_2ND, NLIN_INS_DIR, NLIN_ARCL
	}

	protected boolean showNodeIdFlag_;
	protected boolean showElementIdFlag_;
	protected boolean showElementGroupIdFlag_;

	protected Structure struct_;

	public void disableShowNodeId() {
		this.showNodeIdFlag_ = false;
	}

	public void enableShowNodeId() {
		this.showNodeIdFlag_ = true;
	}

	public void disableShowElementId() {
		this.showElementIdFlag_ = false;
	}

	public void enableShowElementId() {
		this.showElementIdFlag_ = true;
	}

	public void disableShowElementGroupId() {
		this.showElementGroupIdFlag_ = false;
	}

	public void enableShowElementGroupId() {
		this.showElementGroupIdFlag_ = true;
	}

	public ModelViewer() {
		this.enableShowElementGroupId();
		this.enableShowElementId();
		this.enableShowNodeId();
	}

	public Structure createStructure() {
		return null;
	}

	public void initializeVisualizer(Structure struct) {

	}

	public void initializeVisualizer(DynamicStructure struct) {

	}

	public void visualize() {
		// set scaling factors
		d_max_ = viz_.automaticScale();
		// draw
		viz_.drawElements();
		viz_.drawConstraints();
		viz_.drawForces();
		viz_.showNodeId();
		viz_.showElementId();
	}

	public void postVisualize() {
		this.visualize();
		viz_.postAutomaticScale(d_max_);
		// viz.drawDisplacements();
		viz_.drawDisplacedStructure();
		// viz.drawElementForces();
	}

	public void dynamicVisualize(DynamicStructure dStruct) {
		this.visualize();
		viz_.dynamicAutomaticScale(dStruct, d_max_);
		view_.addToSidePanel(createAnimateAdvanceButton(viz_));
		view_.addToSidePanel(createAnimateRetreatButton(viz_));
		view_.addToSidePanel(createAnimateButton(viz_));
	}

	private void redraw() {
		view_.removeAllObjects3D();
		visualize();
		viz_.drawDisplacedStructure();
		view_.render();
	}

	private JButton createAnimateAdvanceButton(final Visualizer viz) {
		JButton b = new JButton("advance");

		b.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				viz.advance();
				redraw();
			}
		});
		return b;
	}

	private JButton createAnimateRetreatButton(final Visualizer viz) {
		JButton b = new JButton("retreat");

		b.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				viz.retreat();
				redraw();
			}
		});
		return b;
	}

	private JButton createAnimateButton(final Visualizer viz) {
		JButton b = new JButton("animate");

		b.addActionListener(new ActionListener() {

			Timer timer;

			class AnimTask extends TimerTask {
				public void run() {
					if (viz.advance())
						timer.cancel();
					redraw();
				}
			}

			public void actionPerformed(ActionEvent e) {
				timer = new Timer();
				timer.schedule(new AnimTask(), 0, // initial delay
						1000); // subsequent rate
			}

		});
		return b;
	}

	public void enableVisualize() {
		view_.setVisible(true);
	}

	public int consoleMenu() {
		System.out
				.println("Please choose the functionality you want to perform:");
		System.out.println(Functionality.VIEW.ordinal()
				+ " - View the structure");
		System.out.println(Functionality.LIN_STA.ordinal()
				+ " - Linear static analysis");
		System.out.println(Functionality.LIN_MODAL.ordinal()
				+ " - Linear modal analysis");
		System.out.println(Functionality.LIN_DYNAMIC.ordinal()
				+ " - Linear dynamic response");
		System.out.println(Functionality.NLIN_STA.ordinal()
				+ " - Nonlinear static analysis");
		System.out.println(Functionality.NLIN_INS_2ND.ordinal()
				+ " - Instability analysis using second order theory");
		System.out.println(Functionality.NLIN_INS_DIR.ordinal()
				+ " - Instability analysis using direct computation");
		System.out.println(Functionality.NLIN_ARCL.ordinal()
				+ " - Load-displacement curve using arc length method");
		System.out.print("You choose: ");

		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);
		String s;
		try {
			s = br.readLine();
			return Integer.parseInt(s);
		} catch (IOException e) {
			System.out.println(e.toString());
		}
		return 0;
	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performViewer(Structure struct) {
		this.initializeVisualizer(struct);
		this.visualize();
		this.enableVisualize();
	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performLinearStaticAnalysis(Structure struct) {
		/*
		 * perform linear static analysis
		 */
		struct.solve();
		// print the structure and the results
		struct.printStructure();
		System.out.println();
		struct.printResults();
		System.out.println("Visualize the structure");
		this.initializeVisualizer(struct);
		this.postVisualize();
		this.enableVisualize();
	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performLinearModalAnalysis(Structure struct) {
		/*
		 * perform linear modal analysis
		 */
		System.out.println("Modal analysis");
		ModalAnalyzer m = new ModalAnalyzer();
		try {
			m.analyze(struct);
			m.printEigenfrequency();
			System.out.println();
			m.printModalVectors();
		} catch (SolveFailedException e) {
			System.out.println(e.toString());
		}
	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performLinearDynamicAnalysis(Structure struct) {
		/*
		 * perform linear dynamic analysis
		 */
		struct.solve();
		DynamicStructure dStruct = new DynamicStructure(struct);
		IVector u0 = dStruct.getStaticDisplacementVector();

		System.out.println("Static displacement vector:");
		System.out.println(MatrixFormat.format(u0));

		LinearTimeIntegrator TI = new LinearTimeIntegrator(dStruct);
		TI.setAlpha1(100);
		TI.setAlpha2(0);
		TI.setTimeStep(1e-4);
		TI.Initialize((int) (1e-1 / TI.getTimeStep()));
		TI.setAccelerationComplementaryConditionAtInitial(true);

		dStruct.addDisplacement(u0);
		IVector temp = (IVector) u0.clone();
		BLAM.fill(temp, 0);
		dStruct.addVelocity(temp);
		// does not need to set the acceleration since the
		// ComplementaryConditionAtInitial is set to true

		// TI.freeVibCDM();
		// TI.freeVibDG1();
		// TI.freeVibDG2();
		TI.freeVibDG(20);

		// dStruct.print();

		// plot command
		oneD.dynamics.Plot1D p = dStruct.plot();
		p.setVisible(true);

		// Viewer v1 = new Viewer();
		// // FunctionPlotter1D fp1 = dStruct.plot1(1, "y", "");
		// FunctionPlotter1D fp1 = dStruct.plot1combination(new int[] { 1 },
		// new String[] { "y" }, new String[] { "" });
		// fp1.plot(v1);
		// v1.setVisible(true);
		//
		// Viewer v2 = new Viewer();
		// FunctionPlotter1D fp2 = dStruct.plot2(0, "x");
		// fp2.plot(v2);
		// v2.setVisible(true);

		System.out.println("Visualize dynamic structure");
		this.initializeVisualizer(dStruct);
		// this.postVisualize();
		this.dynamicVisualize(dStruct);
		this.enableVisualize();

	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performNonlinearStaticAnalysis(Structure struct) {
		/*
		 * nonlinear static deformation solution
		 */
		struct.enumerateDOFs();
		struct.solve(struct.createZeroU());
		struct.printResults();
		System.out.println();
		System.out.println("Visualize the structure");
		this.initializeVisualizer(struct);
		this.postVisualize();
		this.enableVisualize();
	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performNonlinearInstabilityAnalysisUsingSecondOrderTheory(
			Structure struct) {
		/*
		 * instability analysis using second order theory
		 */
		struct.solve();
		IVector u = struct.getU();
		IVector lambda = null;
		struct.secondOrderLoadControlInstabilityComputation(2, u, lambda);

	}

	/*
	 * this is default setting. User must override these method for specific
	 * model/structure
	 */
	protected void performNonlinearInstabilityAnalysisUsingDirectComputation(
			Structure struct) {
		/*
		 * instability analysis using direct computation
		 */
		double lambda = -0.1;
		IVector phi = new ArrayVector(struct.enumerateDOFs());
		BLAM.fill(phi, 0.1);
		IVector u = struct.createZeroU();
		struct.directLoadControlInstabilityComputation(u, lambda, phi);
	}

	/*
	 * derive this method in inherited class
	 */
	protected void performLoadDisplacementAnalysisUsingArcLength(
			Structure struct) {
		struct.enumerateDOFs();
		IVector u = struct.createZeroU();
		double lambda = 0.0;
		ArcLengthControl m = new ArcLengthControl(struct);
		// m.setConstraintType("load control");
		// m.setConstraintType("displacement control");
		m.setU0(u);
		m.setLambda0(lambda);
		m.setConstraintType("sphere");
		m.setParameterS(0.2);
		m.setNumStep(10);
		m.setPsi(1.0);
		// m.run();
		// m.print();
		oneD.fem.method.Plot1D p = m.plot();
		p.setVisible(true);
	}

	protected void manualHandling() {
		// to be overrided in inhertied classes
	}

	public void processUserInput() {
		this.struct_ = this.createStructure();
		this.manualHandling();
		int selection = this.consoleMenu();
		if (selection == Functionality.LIN_STA.ordinal())
			this.performLinearStaticAnalysis(this.struct_);
		else if (selection == Functionality.LIN_MODAL.ordinal())
			this.performLinearModalAnalysis(this.struct_);
		else if (selection == Functionality.LIN_DYNAMIC.ordinal())
			this.performLinearDynamicAnalysis(this.struct_);
		else if (selection == Functionality.NLIN_INS_2ND.ordinal())
			this.performNonlinearInstabilityAnalysisUsingSecondOrderTheory(this.struct_);
		else if (selection == Functionality.NLIN_INS_DIR.ordinal())
			this.performNonlinearInstabilityAnalysisUsingDirectComputation(this.struct_);
		else if (selection == Functionality.NLIN_STA.ordinal())
			this.performNonlinearStaticAnalysis(this.struct_);
		else if (selection == Functionality.VIEW.ordinal())
			this.performViewer(this.struct_);
		else if (selection == Functionality.NLIN_ARCL.ordinal())
			this.performLoadDisplacementAnalysisUsingArcLength(this.struct_);
		else {
		}

	}

}
